package com.meeting.meetingroom_api.service.impl;

import com.meeting.meetingroom_api.constants.ErrorMessages;
import com.meeting.meetingroom_api.dto.PaymentDto;
import com.meeting.meetingroom_api.dto.PaymentInitiateDto;
import com.meeting.meetingroom_api.dto.PaymentNotificationDto;
import com.meeting.meetingroom_api.entity.Booking;
import com.meeting.meetingroom_api.entity.MeetingRoom;
import com.meeting.meetingroom_api.entity.Payment;
import com.meeting.meetingroom_api.entity.User;
import com.meeting.meetingroom_api.enums.BookingStatus;
import com.meeting.meetingroom_api.enums.PaymentStatus;
import com.meeting.meetingroom_api.enums.RoomStatus;
import com.meeting.meetingroom_api.repository.BookingRepository;
import com.meeting.meetingroom_api.repository.MeetingRoomRepository;
import com.meeting.meetingroom_api.repository.PaymentRepository;
import com.meeting.meetingroom_api.repository.UserRepository;
import com.meeting.meetingroom_api.service.BookingService;
import com.meeting.meetingroom_api.service.PaymentService;
// import com.meeting.meetingroom_api.adapter.PaymentGatewayAdapter; // Hypothetical adapter
import jakarta.persistence.EntityNotFoundException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

@Service
public class PaymentServiceImpl implements PaymentService {

    private final PaymentRepository paymentRepository;
    private final BookingRepository bookingRepository;
    private final UserRepository userRepository;
    private final MeetingRoomRepository meetingRoomRepository;
    private final BookingService bookingService; // To update booking status
    // private final PaymentGatewayAdapter paymentGatewayAdapter; // Hypothetical

    @Autowired
    public PaymentServiceImpl(PaymentRepository paymentRepository,
                              BookingRepository bookingRepository,
                              MeetingRoomRepository meetingRoomRepository,
                              UserRepository userRepository,
                              BookingService bookingService
            /*, PaymentGatewayAdapter paymentGatewayAdapter */) {
        this.paymentRepository = paymentRepository;
        this.bookingRepository = bookingRepository;
        this.meetingRoomRepository = meetingRoomRepository;
        this.userRepository = userRepository;
        this.bookingService = bookingService;
        // this.paymentGatewayAdapter = paymentGatewayAdapter;
    }

    @Override
    @Transactional
    public PaymentDto createInitialPaymentRecord(Booking booking) {
        if (paymentRepository.findByBooking(booking).isPresent()) {
            throw new IllegalStateException(String.format(ErrorMessages.PAYMENT_RECORD_ALREADY_EXISTS, booking.getBookingId()));
        }
        Payment payment = new Payment();
        payment.setBooking(booking);
        payment.setAmount(booking.getTotalPrice());
        payment.setStatus(PaymentStatus.pending); // Initial status
        Payment savedPayment = paymentRepository.save(payment);
        return convertToDto(savedPayment);
    }


    @Override
    @Transactional
    public Object initiatePayment(PaymentInitiateDto initiateDto, Integer customerId) {
        User customer = userRepository.findById(customerId)
                .orElseThrow(() -> new EntityNotFoundException("Customer not found with ID: " + customerId));

        Booking booking = bookingRepository.findById(initiateDto.getBookingId())
                .orElseThrow(() -> new EntityNotFoundException("Booking not found with ID: " + initiateDto.getBookingId()));

        if (!booking.getCustomer().getUserId().equals(customerId)) {
            throw new SecurityException(ErrorMessages.CUSTOMER_NOT_OWN_BOOKING);
        }

        if (booking.getStatus() != BookingStatus.pending_payment) {
            throw new IllegalStateException(String.format(ErrorMessages.BOOKING_NOT_PENDING_PAYMENT, booking.getStatus()));
        }

        if (booking.getPaymentDeadline() != null && LocalDateTime.now().isAfter(booking.getPaymentDeadline())) {
            //bookingService.updateBookingStatus(booking.getBookingId(), BookingStatus.cancelled_payment_timeout, null);
            throw new IllegalStateException(String.format(ErrorMessages.PAYMENT_DEADLINE_PASSED, booking.getBookingId()));
        }

        Payment payment = paymentRepository.findByBooking(booking)
                .orElseGet(() -> { // Create if not exists (should have been created with booking)
                    Payment newPayment = new Payment();
                    newPayment.setBooking(booking);
                    newPayment.setAmount(booking.getTotalPrice());
                    newPayment.setStatus(PaymentStatus.pending);
                    return paymentRepository.save(newPayment);
                });

        if (payment.getStatus() == PaymentStatus.successful) {
            throw new IllegalStateException(ErrorMessages.PAYMENT_ALREADY_SUCCESSFUL);
        }

        payment.setPaymentMethod(initiateDto.getPaymentMethod());
        payment.setStatus(PaymentStatus.pending); // Update status
        paymentRepository.save(payment);

        // ** Integration with Payment Gateway **
        // This is where you'd call the paymentGatewayAdapter
        // Object gatewayResponse = paymentGatewayAdapter.initiatePayment(payment, initiateDto.getSuccessUrl(), initiateDto.getCancelUrl());
        // return gatewayResponse; // This could be a redirect URL, or data for a client-side SDK

        // Placeholder response if no gateway integrated
        System.out.println("Mock Payment Initiation for Booking ID: " + booking.getBookingId() +
                " with method: " + initiateDto.getPaymentMethod());
        // Simulate returning some data or a mock redirect
        return "{\"message\": \"Payment initiated, redirect to mock payment page for booking " + booking.getBookingId() + "\", \"redirectUrl\": \"/mock-payment-page?bookingId=" + booking.getBookingId() + "\"}";
    }

    //将会议室改为Booked前，先判断是否有已有其它活跃订单正在待支付，会议室改为locked，正在使用，会议室改为in_use，否则为booked。
    //测试通过
    @Override
    @Transactional
    public PaymentDto confirmPayment(Integer bookingId, Integer customerId) {
        // 验证客户身份
        User customer = userRepository.findById(customerId)
                .orElseThrow(() -> new EntityNotFoundException("Customer not found with ID: " + customerId));

        // 获取预订信息
        Booking booking = bookingRepository.findById(bookingId)
                .orElseThrow(() -> new EntityNotFoundException("Booking not found with ID: " + bookingId));

        // 验证预订归属
        if (!booking.getCustomer().getUserId().equals(customerId)) {
            throw new SecurityException(ErrorMessages.CUSTOMER_NOT_OWN_BOOKING);
        }

        // 验证预订状态
        if (booking.getStatus() != BookingStatus.pending_payment) {
            throw new IllegalStateException(String.format(ErrorMessages.BOOKING_NOT_PENDING_PAYMENT, booking.getStatus()));
        }

        // 检查支付截止时间
        if (booking.getPaymentDeadline() != null && LocalDateTime.now().isAfter(booking.getPaymentDeadline())) {
            //bookingService.updateBookingStatus(booking.getBookingId(), BookingStatus.cancelled_payment_timeout, null);
            throw new IllegalStateException(String.format(ErrorMessages.PAYMENT_DEADLINE_PASSED, booking.getBookingId()));
        }

        // 获取支付记录
        Payment payment = paymentRepository.findByBooking(booking)
                .orElseThrow(() -> new EntityNotFoundException("Payment record not found for booking ID: " + bookingId));

        // 验证支付状态
        if (payment.getStatus() == PaymentStatus.successful) {
            throw new IllegalStateException(ErrorMessages.PAYMENT_ALREADY_SUCCESSFUL);
        }

        //查询活跃其它活跃订单更新会议室状态。
        List<BookingStatus> activeStatuses = Arrays.asList(
                BookingStatus.pending_payment,
                BookingStatus.confirmed,
                BookingStatus.in_progress
        );
        MeetingRoom room = booking.getRoom();
        //查询具体活跃订单，改变会议室对应状态
        List<Booking> activeBookings = bookingRepository.findByRoomRoomIdAndStatusInAndBookingIdNotAndNotExpired(
                room.getRoomId(),
                activeStatuses,
                bookingId // 排除当前订单（当前订单ID）
        );


        // 模拟支付成功
        payment.setStatus(PaymentStatus.successful);
        payment.setPaidAt(LocalDateTime.now());
        payment.setTransactionId("MOCK_TXN_" + System.currentTimeMillis()); // 生成模拟交易ID
        payment.setPaymentGatewayResponse("Mock payment confirmation for campus project");

        // 更新预订状态为已确认
        bookingService.updateBookingStatus(booking.getBookingId(), BookingStatus.confirmed, null);

        Payment updatedPayment = paymentRepository.save(payment);

        // 注释：会议室状态现在只通过计算得出，不再直接设置
        // RoomStatus newStatus = determineRoomStatus(activeBookings);
        // if (room.getStatus() != RoomStatus.maintenance) {
        //     room.setStatus(newStatus);
        //     meetingRoomRepository.save(room);
        // }

        System.out.println("Mock payment confirmed for booking ID: " + bookingId + " by customer ID: " + customerId);

        return convertToDto(updatedPayment);
    }

    //查询订单状态，支付成功，对应状态booked之前，判断其它活跃订单的状态。
    private RoomStatus determineRoomStatus(List<Booking> activeBookings) {
        // 优先级：pending_payment > in_progress > confirmed
        if (activeBookings.stream().anyMatch(b -> b.getStatus() == BookingStatus.pending_payment)) {
            return RoomStatus.locked;
        } else if (activeBookings.stream().anyMatch(b -> b.getStatus() == BookingStatus.in_progress)) {
            return RoomStatus.in_use;
        } else {
            //没有其它待支付、正在使用中的订单。支付成功，变成booked
            return RoomStatus.booked;
        }
    }

    @Override
    @Transactional
    public PaymentDto handlePaymentNotification(PaymentNotificationDto notificationDto) {
        // Validate notification (e.g., signature check from gateway - VERY IMPORTANT)

        // Find payment by orderId (which should be our bookingId or paymentId)
        // Assuming orderId from gateway maps to our bookingId for simplicity
        Integer bookingId;
        try {
            bookingId = Integer.parseInt(notificationDto.getOrderId());
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException(String.format(ErrorMessages.INVALID_ORDER_ID_FORMAT, notificationDto.getOrderId()));
        }

        Payment payment = paymentRepository.findByBooking_BookingId(bookingId)
                .orElseThrow(() -> new EntityNotFoundException("Payment record not found for booking ID: " + bookingId + " from notification."));

        // Idempotency: Check if this transaction has already been processed
        if (notificationDto.getTransactionId() != null && payment.getTransactionId() != null &&
                payment.getTransactionId().equals(notificationDto.getTransactionId()) &&
                payment.getStatus() == notificationDto.getStatus()) {
            System.out.println("Duplicate payment notification received and ignored for transaction: " + notificationDto.getTransactionId());
            return convertToDto(payment);
        }

        payment.setTransactionId(notificationDto.getTransactionId());
        payment.setPaymentGatewayResponse(notificationDto.getRawPayload()); // Store raw response

        if (notificationDto.getStatus() == PaymentStatus.successful) {
            payment.setStatus(PaymentStatus.successful);
            payment.setPaidAt(notificationDto.getPaymentTimestamp() != null ? notificationDto.getPaymentTimestamp() : LocalDateTime.now());
            // Update Booking status
            bookingService.updateBookingStatus(payment.getBooking().getBookingId(), BookingStatus.confirmed, null);
        } else if (notificationDto.getStatus() == PaymentStatus.failed) {
            payment.setStatus(PaymentStatus.failed);
            // Optionally update booking status back to PENDING_PAYMENT or CANCELLED if payment fails definitively
            // For now, let's assume booking remains PENDING_PAYMENT unless explicitly cancelled.
            // bookingService.updateBookingStatus(payment.getBooking().getBookingId(), BookingStatus.PAYMENT_FAILED, null); // Or a new BookingStatus
        } else {
            // Handle other statuses from gateway if necessary
            payment.setStatus(notificationDto.getStatus()); // Or map to your internal statuses
        }

        Payment updatedPayment = paymentRepository.save(payment);
        return convertToDto(updatedPayment);
    }

    @Override
    public PaymentDto getPaymentStatusByBookingId(Integer bookingId) {
        Payment payment = paymentRepository.findByBooking_BookingId(bookingId)
                .orElseThrow(() -> new EntityNotFoundException("Payment not found for booking ID: " + bookingId));
        return convertToDto(payment);
    }

    @Override
    @Transactional
    public PaymentDto processRefund(Integer bookingId, String reason, Integer adminId) {
        User admin = userRepository.findById(adminId)
                .orElseThrow(() -> new EntityNotFoundException("Admin user not found with ID: " + adminId));
        // Add role check for admin

        Booking booking = bookingRepository.findById(bookingId)
                .orElseThrow(() -> new EntityNotFoundException("Booking not found with ID: " + bookingId));

        Payment payment = paymentRepository.findByBooking(booking)
                .orElseThrow(() -> new EntityNotFoundException("Payment not found for booking ID: " + bookingId));

        if (payment.getStatus() != PaymentStatus.successful && payment.getStatus() != PaymentStatus.refunded) {
            throw new IllegalStateException(String.format(ErrorMessages.PAYMENT_NOT_REFUNDABLE, payment.getStatus()));
        }

        // ** Integration with Payment Gateway for Refund **
        // boolean refundSuccess = paymentGatewayAdapter.processRefund(payment.getTransactionId(), payment.getAmount(), reason);
        boolean refundSuccess = true; // Placeholder

        if (refundSuccess) {
            payment.setStatus(PaymentStatus.refunded);
            // Update booking status to CANCELLED or a specific "REFUNDED" status

            bookingService.updateBookingStatus(bookingId, BookingStatus.cancelled_by_customer, adminId); // Or a more specific status
            // Log refund action, potentially in a new "Refunds" table or audit log
            System.out.println("Refund processed for booking ID: " + bookingId + " by admin ID: " + adminId + ". Reason: " + reason);
        } else {
            payment.setStatus(PaymentStatus.refunded); // Or keep as SUCCESSFUL if gateway handles retries
            System.err.println("Refund failed for booking ID: " + bookingId + ". Gateway processing failed.");
            // Potentially throw an exception or return a DTO indicating failure
        }
        System.out.println("Payment status after refund: " + payment.getStatus());
        Payment updatedPayment = paymentRepository.save(payment);
        return convertToDto(updatedPayment);
    }


    private PaymentDto convertToDto(Payment payment) {
        PaymentDto dto = new PaymentDto();
        BeanUtils.copyProperties(payment, dto);
        if (payment.getBooking() != null) {
            dto.setBookingId(payment.getBooking().getBookingId());
        }
        return dto;
    }
}